package v3;
//First working Draft of the Quick-and-Dirty Implementation
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.List;

import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;

/**Space Invaders game from http://kgdug.org. by MohJB@hotmail.com, The Quick and Dirty implementation*/
public class QD extends JPanel implements MouseListener
, MouseMotionListener, ComponentListener, KeyListener,Runnable {


/**a reference of the window of the application, the reference is used for repaint*/
JFrame frame;

/**a reference of the animation thread, the reference is used to check if the thread should exit.*/
Thread thread;

/**only for demo-ing the distance between the player and the nearest invader-bullet*/
static double dist;

 static int 
 /**an int that plays the role of a score, where it is the count of how many invaders have been killed by the player*/ 
 kills=0
 
 ,/**count of how many times the player was hit, below zero*/
 lives=0
 
 ,/**how many waves the player has completed, in other words, how many times invader teams have been born*/
 level=0
 
 ,/***/mouseButtons;

 static GO 
 /**a reference of a basic(singleton, a.k.a. global-variable) Graphical-Object for the player*/
 player
 
 ,/**the nearest invader-bullet to the player, this is only for demo-ing purposes*/
 near;
 
 /**list of the (current-wave) invaders ,(singleton, a.k.a. global-variable) */
 static InvaderGOs invaders;

 /**a pool of player bullets, holds both active and available bullets, the list is not reordered,(singleton, a.k.a. global-variable)*/
 static BulletGOs bullets;

/**Directory path of the location of resources (picture/image files)*/ 
 final static String PATH_MONSTERS="v3/monsters/",PATH_BULLETS="v3/bullets/";

 /**constructor that prepares the GUI,and event-listeners, and initializes the game, and starts the animation thread*/
 public QD(){frame=new JFrame(
		"Space Invaders from http://kgdug.org, QuickAndDirtyImplementation by MohJB");
	frame.setContentPane(this);
	frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	frame.setBounds(10,10, 800, 600);
	player=new GO();
	invaders=new InvaderGOs();invaders.init(10,20);
	bullets=new BulletGOs();bullets.init(45);
	addMouseListener(this);
	addMouseMotionListener(this);
	addKeyListener(this);
	addComponentListener(this);
	frame.setVisible(true);
	setPanelDim(getSize());//init the size of the window(panel)
	thread=new Thread(this);
	thread.start();}//start the animation

/**the main method for DRAWING, maybe the most important thing in this application */
 public void paint(Graphics g)
 {Dimension d=getPanelDim();long now=System.currentTimeMillis();
  g.clearRect(0,0, d.width ,d.height);//erase the screen each time we want to draw a frame, for animation.
  g.drawString("kills:"+kills,10,10);//show the (label of) player score 
  g.drawString("lives:"+lives,100,10);//show the (label of) how many times the player has been hit by invader-bullets
  g.drawString("level:"+level,200,10);//show (label of) how many times the user completed a game.
  g.drawString("invadersCount:"+invaders.count,10,30);//the count of the current active(on) invaders, that are showing and shooting.
  g.drawString("nearest:"+dist,10,40);//show (label of) distance of the closest invader-bullet to the player
	invaders.paint(g,now);//call the paint method of the list of invaders, in other words, this is where the animation thread goes to the call of draw all the active/"on" invaders and their bullets
	bullets.paint(g,now);//this line is where the animation thread goes inside the method, to draw all the player bullets.
	player.paint(g,now);// this line is where the animation thread draws the player
	//if(near!=null)g.drawLine(player.x,player.y, near.x, near.y);//only for demo-ing purposes
 }

 /**this is where the animation thread starts. 
  * The MAIN ENTRY PIONT for the animation, Thread-of-Execution
  * the main loop that keeps continuosly looping so that the screen keeps changing(animation)*/
 public void run()
 {Thread current=Thread.currentThread();//a reference of the current running thread, used for the while-loop
	while(thread==current)// condition to keep alive the animation, if the global/singleton reference is still not null.
	try{Dimension d=getPanelDim();// a reference to the dimensions of the window to draw on
		invaders.incr(d);//tell the invaders to move, along with their bullets
		bullets.incr(d);// tell the player bullets to move
		if(mouseButtons!=0)bullets.shootNew();
		frame.repaint();// command the the GUI to repaint 
		thread.sleep(75);// sleep so that the Operating-System doesnt hang, also so the the user can see the current from of the animation before starting the next frame. the parameter is in terms of milli-seconds
	}catch(Exception ex){ex.printStackTrace();}}//if there was an exception(error) then print it on the console
////////////////////////////////////////////////////////////////
 /**GO=Graphical Object
  * a super class for all the shapes that will be drawn on the screen. this approach( of having one super-class or base-class) is for a polymorphic methodology.
  * this is an Object-Oriented approach to building an architecture of this software-application.
  * */
 static class GO
 {int x,y,dx,dy;boolean on; SeqFrame seq=new SeqFrame();

	boolean getOn(){return on;}
	void setOn(boolean p)
	{if(p!=on){if(on=p)fireOn();else fireOff();}}

	SeqFrame getSeq(){return seq;}

	void paint(Graphics g,long now){getSeq().paint(g, x, y,now);}

	/**increment*/
	void incr(Dimension d)
	{x+=dx;y+=dy;Dimension dim=getSeq().dim();
	 //int w2=dim.width/2,h2=dim.height/2;
	 if(x+dim.width+2<0||x-dim.width>=d.width
		||y+dim.height<0||y-dim.height>=d.height)setOn(false);
	}

	boolean isCollide(GO o)
	{if(!on||o==null||!o.on)return false;
	 Dimension dim=getSeq().dim(),d2=o.getSeq().dim();
	 int w1=dim.width/2,h1=dim.height/2
	 ,w2=d2.width/2,h2=d2.height/2;
	 return on&&o.on
		&&x-w1<o.x+w2
		&&x+w1>o.x-w2
		&&y-h1<o.y+h2
		&&y+h1>o.y-h1;}

	double dist(GO p){double a=x-p.x,b=y-p.y;return Math.sqrt(a*a+b*b);}

	GO isCollide(GOs a)
	{if(on)
		for(GO o:a.gos)
			if(isCollide(o))
				return o;
	 return null;}

	boolean checkCollide(GO o)
	{if(isCollide(o))
	 {fireCollide(o);o.fireCollide(this);
		setOn(false);o.setOn(false);
		return true;}
	 return false;}

	List<GOListener>listeners;

	void addGOListener(GOListener gol)
	{if(listeners==null)listeners=new ArrayList<GOListener>();
	 if(!listeners.contains(gol))listeners.add(gol);}

	void removeGOListener(GOListener gol)
	{if(listeners==null)return;
	 listeners.remove(gol);}

	void fireOn( ){if(listeners!=null)for(GOListener l:listeners)l.onOn (this);}
	void fireOff(){if(listeners!=null)for(GOListener l:listeners)l.onOff(this);}
	void fireCollide(GO p2){if(listeners!=null)for(GOListener l:listeners)l.onCollide(this,p2);}

 }//class GO=Graphical Object
//////////////////////////////////////////////////////////////////
 /**Sequence animation frames of images */
 static class Seq
 {Dimension seqDim;
	BufferedImage[]imgs;
	int framesCount=2;
	long duration=500;

	//void setDim(int i,int w,int h){if(seqDim==null)seqDim=new Dimension(w,h); else{seqDim.width=w;seqDim.height=h;}}

	Dimension dim(int i)/*, Dimension d)
	/*{if(imgs==null||imgs.length<=i)
	 d.setSize(tileDim.width, tileDim.height);
	 else{BufferedImage g=imgs[i];
	 if(g==null)d.setSize(tileDim.width, tileDim.height);
	 else d.setSize(g.getWidth(), g.getHeight());}
	 return d;}*/{return seqDim==null?tileDim:seqDim;}

	BufferedImage img(int i){return imgs[i];}

	//void addImg(BufferedImage p){imgs[frameIndex]=p;setDim(p.getWidth(),p.getHeight());}

	void loadImage2(String prefix,String[]fileNames)
	{imgs=new BufferedImage[fileNames.length];
	 for(int i=0;i<fileNames.length;i++)
		try{imgs[i]=ImageIO.read(ClassLoader.getSystemResourceAsStream(//new File(
				prefix+fileNames[i]));
			System.out.println("read image file:"+imgs[i]);
			int w=imgs[i].getWidth(),h=imgs[i].getHeight();
			if(seqDim==null){seqDim=new Dimension(w,h);}
			else{if(seqDim.width<w)seqDim.width=w;
				if(seqDim.height<h)seqDim.height=h;}
		}catch(Exception ex){ex.printStackTrace();}}

	void loadImages(String prefix,String...fileNames)
	{loadImage2(prefix,fileNames);}

	//Seq(String prefix,String[]fileNames){loadImages(prefix,fileNames);}
	Seq(String prefix,String...fileNames){loadImage2(prefix,fileNames);}

	int getFramesCount(){return framesCount;}

 }//class Seq
////////////////////////////////////////////////////////////////
 static class SeqFrame
 {long durationTarget;
  int frameIndex;Seq seq;
 	int getFrameIndex( ){return frameIndex ;}
 	void setFrameIndex (int p){frameIndex=p;}
 	void nextFrameIndex(	 ){frameIndex++;
 	if(frameIndex>=seq.framesCount){
 		frameIndex=0;fireSFEnd();}else{ fireSFFrame();}}

 	SeqFrame(){}
 	SeqFrame(Seq s){seq=s;}
 	
 	BufferedImage img(){return seq==null?null:seq.imgs==null?null:seq.imgs.length<=frameIndex?null:seq.imgs[frameIndex];}
 
 	Dimension dim(){return seq==null?QD.tileDim:seq.dim(frameIndex);}
 
 	void paintVector(Graphics g,int x,int y,int w,int h)
 	{g.drawOval(x-w/2, y-h/2, w, h);}

 	void paint(Graphics g,int x,int y,long now)
 	{if(now>=durationTarget&&seq!=null)
 		{durationTarget=now+seq.duration;nextFrameIndex();}
 		Dimension d=dim();BufferedImage i=img();
 		int w=d.width,w2=w/2,h=d.height,h2=h/2;
 		if(i==null)paintVector(g,x, y, w, h);
 		else g.drawImage(i, x-w2, y-h2 ,null,null);}

	List<SeqFrameListener>listeners;

	void addSFListener(SeqFrameListener l)
	{if(listeners==null)listeners=new ArrayList<SeqFrameListener>();
	 if(!listeners.contains(l))listeners.add(l);}

	void removeGOListener(SeqFrameListener gol)
	{if(listeners==null)return;
	 listeners.remove(gol);}

	void fireSFEnd( ){if(listeners!=null)for(SeqFrameListener l:listeners)l.onSFEnd (this);}
	void fireSFFrame(){if(listeners!=null)for(SeqFrameListener l:listeners)l.onSFrame(this);}

 }//class SeqFrame
////////////////////////////////////////////////////////////////
 interface SeqFrameListener{void onSFEnd(SeqFrame sf);void onSFrame(SeqFrame sf);}
////////////////////////////////////////////////////////////////
 //class SeqBlast extends SeqFrame {void paintVector(Graphics g,int x,int y){}}//class SeqBlast
////////////////////////////////////////////////////////////////
 interface GOListener{void onOn(GO go);void onOff(GO go);void onCollide(GO go,GO g2);}
//////////////////////////////////////////////////////////////////
 static class Bullet extends GO{Bullet(){x=y=dx=0;dy=-30;
	seq.seq=seqBullets[bulletIx];
	bulletIx=(bulletIx+1)%seqBullets.length;}

	static Seq blast=new Seq(PATH_BULLETS,"blast.png","blast1.png")
	,seqBullets[]={new Seq(PATH_BULLETS,"bullet.png","bullet.png")
	//,new Seq(PATH_BULLETS,"bullet.gif")
	//,new Seq(PATH_BULLETS,"marioUp.png")
	,new Seq(PATH_BULLETS,"marioUp21x32.png","marioUp21x32.png")};
	static int bulletIx=0;
 }
////////////////////////////////////////////////////////////////
 static class Invader extends GO implements GOListener
 {static Seq seqs[]={new Seq(PATH_MONSTERS,"blue.png","blue1.png")
	,new Seq(PATH_MONSTERS,"red.png","red1.png")
	,new Seq(PATH_MONSTERS,"green.png","green1.png")
	//,new Seq(PATH_MONSTERS,"mike0.png","mike1.png")
	,new Seq(PATH_MONSTERS,"mik0.png","mik1.png")
	,new Seq(PATH_MONSTERS,"ghost.png","ghost1.png")}

 , seqBullets[]={new Seq(PATH_MONSTERS,"bullet.png","bullet.png")
	,new Seq(PATH_MONSTERS,"dag.png","dag.png")};
 Bullet bullet;long wait;
static int bulletIx=0;
  Invader(){bullet=new Bullet();
	bullet.seq.seq=seqBullets[bulletIx];
	bulletIx=(bulletIx+1)%seqBullets.length;
	bullet.addGOListener(this);dx=3;bullet.dy=15;}

//  SeqFrame getSeq(){return seqs[iSeq];}

  void paint(Graphics g,long now){if(bullet.on){//g.drawLine(bullet.x, bullet.y, x, y);
  bullet.paint(g,now);}super.paint(g,now);}

  void init(int x,int y){Dimension d=seq.dim();this.x=x+d.width/2;this.y=y+d.height/2;shoot(10);setOn(true);}

  void incr(Dimension d)
	{if(!on)return;
	 Dimension dim=getSeq().dim();
	 int w2=dim.width/2,h2=dim.height/2;
	 if((dx>0&&x+dx-w2>=d.width)||(dx<0&&x+dx<=w2+5))
	 {y+=dim.height;dx=dx*-1;}
	 if(y+h2>=d.height)y=h2;
	 super.incr(d);
	 if(wait>0)
	 {wait--;if(wait==0)
	  {wait--;shoot();}}
	}

  void shoot(long p){wait=(long)(Math.random()*p);}
  void shoot(){bullet.x=x;bullet.y=y;bullet.setOn(true);}
  public void onOn(GO go){}
  public void onOff(GO go){shoot(10);}
  public void onCollide(GO go,GO g2){shoot(5);}
 }//class Invader
////////////////////////////////////////////////////////////////
 abstract class GOs
 {List<GO>gos;

	void paint(Graphics g,long now)
	{for(GO o:gos)if(o.on)o.paint(g,now);}

	void incr(Dimension d){for(GO o:gos)if(o.on)o.incr(d);}

	abstract GO instance();

	GOs init(int n){gos=new ArrayList<GO>(n);
		for(int i=0;i<n;i++)gos.add(instance());return this;}

 }//class GOs
////////////////////////////////////////////////////////////////
 class BulletGOs extends GOs
 {GO instance(){return new Bullet();}

  void shootNew(){for(GO b:gos)if(!b.on){b.x=player.x;b.y=player.y;b.setOn(true);return;}}

  void incr(Dimension d)
  {for(GO bullet:gos)
	if(bullet.on)
	{bullet.incr(d);
	  for(GO invader:QD.invaders.gos)
		if(invader.on && bullet.checkCollide(invader))
			kills++;
	}
  }
 }//class BulletGOs
////////////////////////////////////////////////////////////////
 class InvaderGOs extends GOs
 {GO instance(){return new Invader();}
	int count,rows,cols;
	Bullet bullet;Dimension biggest=new Dimension(1,1);

  GOs init(int r,int c){init(r*c);rows=r;cols=c;init();return this;}

  void init()
  {Seq seq=Invader.seqs[0];//gos.get(0).getSeq();
   Dimension d=seq.dim(0),p=QD.getPanelDim(),id;//,mx;
	//count=rows*cols;
	level++;
	if(p==null)QD.setPanelDim(p=getSize());
/*
	for(GO g:gos){mx=g.getSeq().dim();
		if(biggest.width <mx.width )biggest.width =mx.width;
		if(biggest.height<mx.height)biggest.height=mx.height;}
	mx=biggest;
	/*int n=gos.size()
	,ox=(p.width- mx.width *cols  )/cols
	,oy=(p.height-mx.height*rows)/(rows*2)
	,x,y,i=0,fi=0;
	for(int r=0;r<rows;r++)
	{	y=1+r*oy;
		for(int c=0;c<cols;c++)
		{Invader invader=(Invader)gos.get(i++);
			x=1+c*ox;
			invader.init(x, y);
			//if(invaders_imgs!=null)
			{invader.seq.seq=Invader.seqs[fi];
				fi=(fi+1)%Invader.seqs.length;
			}
		}
	}*/
	int n=gos.size(),h=16,x=0,y=0,i=0,fi=0;
	while(i<n&&y<p.height)
	{Invader invader=(Invader)gos.get(i++);
		invader.init(x, y);
		id=invader.seq.dim();
		x+=id.width;
		h=Math.max(h,id.height);
		//if(invaders_imgs!=null)
		invader.seq.seq=Invader.seqs[fi];
		fi=(fi+1)%Invader.seqs.length;
		if(x>p.width){x=0;y+=h;h=16;}
	}
  }

  void incr(Dimension d)
  {count=0;near=null;
	double t=0;dist=d.width*2+d.height*2;
	for(GO invader:gos)if(invader.on)
	{count++;invader.incr(d);
		Bullet iBullet=((Invader)invader).bullet;
		if(iBullet.on)
		{iBullet.incr(d);
		 t=QD.player.dist(iBullet);
		 if(t<dist){dist=t;near=iBullet;}
		 if( QD.player.checkCollide( iBullet))
		 {lives--;QD.player.setOn(true);}
		 else if(t<8){lives--;QD.player.setOn(true);iBullet.setOn(false);}
		 else
		 {GO b=iBullet.isCollide(QD.bullets);
		  if(b!=null)
			iBullet.checkCollide(b);
		 }
		}
	}
   if(count==0)init();
  }
 }//class InvaderGOs
////////////////////////////////////////////////////////////////

	static Dimension _dim,tileDim=new Dimension(16,16);
	static Dimension getPanelDim(){return _dim;}
	static void setPanelDim(Dimension p)
	{System.out.println("setPanelDim:"+p);
		_dim=p;
		player.y=p.height-player.getSeq().dim().height;}

	@Override	public void componentResized(ComponentEvent e)
	{setPanelDim(this.getSize());}

	@Override	public void mouseMoved(MouseEvent e){player.x=e.getX();}
	@Override	public void mouseDragged(MouseEvent e) {player.x=e.getX();}
	@Override	public void mouseReleased(MouseEvent e){mouseButtons-=e.getButton();}
	@Override	public void mousePressed(MouseEvent e) {mouseButtons+=e.getButton();}

	@Override	public void keyReleased(KeyEvent e)
	{//if fire button pressed
		bullets.shootNew();}

	@Override	public void keyTyped(KeyEvent e) {}
	@Override	public void keyPressed(KeyEvent e) {}
	@Override	public void componentMoved(ComponentEvent e) {}
	@Override	public void componentShown(ComponentEvent e) {}
	@Override	public void componentHidden(ComponentEvent e) {}
	@Override	public void mouseClicked(MouseEvent e) {}
	@Override	public void mouseEntered(MouseEvent e) {}
	@Override	public void mouseExited(MouseEvent e) {}

	/** entry point for main program.	 */
	public static void main(String[] args) {new QD();}

}//QD